物件導向基礎與 prototype


Posted by ericcch24 on 2020-10-16

什麼是物件導向

從閉包那邊的程式碼來看

function getWallet() {
  var my_balance = 999
  return {
    deduct: function(n) {
      my_balance -= (n > 10 ? 10 : n) // 超過 10 塊只扣 10 塊
    }
  }
}

var wallet = getWallet()
wallet.deduct(13) // 只被扣 10 塊
my_balance -= 999 // Uncaught ReferenceError: my_balance is not defined

其中 wallet.deduct(13) 表示對 wallet 這個物件呼叫裡面的 function,這差不多是物件導向的概念,==也就是要對某個物件作操作==。
而一般直接呼叫 function 的寫法是這樣 deduct(wallet)


class 範例實作物件導向(ES6)

class Dog {
  constructor(name) {
    this.name = name 
    // this 指到下面的 instance 所呼叫的不同變數
  }

  getName() {
    return this.name
  }

  sayHello() {
    console.log(this.name)
  }
}

var d = new Dog('abc') // 這叫 instance
// new -> 就是呼叫 class Dog 內的 constructor 建構子

// 因為 d 呼叫了 class Dog 內的 constructor
// 所以上面 constructor 的 this 就會指到 d 這個變數

d.sayHello()  // abc


var b = new Dog('blabla')
b.sayHello()  // blabla

ES5 的 class

// constructor
function Person(name, age) {
  this.name = name;
  this.age = age;
}

var nick = new Person('nick', 18); // instance
var peter = new Person('peter', 18); // instance

Person是一個構造函數,可以用new這個關鍵字 new 出一個 instance 來。

除此之外,也可以幫Person加入一些方法。

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.log = function () {
    console.log(this.name + ', age:' + this.age);
  }
}

var nick = new Person('nick', 18);
nick.log(); // nick, age:18

var peter = new Person('peter', 20);
peter.log(); // peter, age:20

可是這樣其實還有一個小問題, name 跟 age 這兩個屬性,很明顯是每一個 instance 都會不一樣的。可是 log 這個 method,其實是每一個 instance 彼此之間可以共享的,因為都在做同一件事情。

在現在這種情況下,雖然 nick 的 log 這個 function 跟 peter 的 log 這個 function 是在做同一件事,但其實還是佔用了兩份空間,意思就是他們其實是兩個不同的 function。

那怎麼辦呢?我們可以把這個 function 抽出來,變成所有 Person 都可以共享的方法。講到這邊,你應該有聽過一個東西叫做 prototype。只要把 log 這個 function 指定在 Person.prototype 上面,所有 Person 的 instance 都可以共享這個方法。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}

var nick = new Person('nick', 18);
var peter = new Person('peter', 20);

console.log(nick.log === peter.log) // true

// 功能依舊跟之前一樣
nick.log(); // nick, age:18
peter.log(); // peter, age:20

小總結

你有一個叫做Person的函數,就可以把Person當作 constructor,利用var obj = new Person()來 new 出一個Person的 instance,並且可以在Person.prototype上面加上你想讓所有 instance 共享的屬性或是方法。

參考資料:該來理解 JavaScript 的原型鍊了


教學影片範例

function Dog(name) {
  this.name = name
}

Dog.prototype.getName = function() {
  return this.name
}

Dog.prototype.sayHello = function() {
  console.log(this.name)
}

var d = new Dog('abc')
// 使用 new 的 function(這裡指Dog()) 就會被
// 當成 constructor 使用

d.sayHello() 
// 可以呼叫上面的 Dog.prototype.sayHello

var b = new Dog('blabla')
b.sayHello()

探究原型鍊原理

以上面var nick = new Person('nick', 18);的例子來說,當我在呼叫nick.log()的時候,JavaScript 是怎麼找到這個 function 的?

因為 nick 這個 instance 本身並沒有 log 這個 function。==但根據 JavaScript 的機制,nick 是 Person 的 instance,所以如果在 nick 本身找不到 log,它會試著從Person.prototype去找。==

==nick 跟Person.prototype會透過__proto__的方式連接起來,才知道說要往哪邊去找 log 這個 function。==

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}

var nick = new Person('nick', 18);

console.log(nick.__proto__ === Person.prototype) // true

nick 的__proto__會指向Person.prototype,所以在發現 nick 沒有 log 這個 method 的時候,==JavaScript 就會試著透過__proto__找到Person.prototype==,去看Person.prototype裡面有沒有 log 這個 method。

假如Person.prototype還是沒有,就繼續依照這個規則,去看Person.prototype.__proto__裡面有沒有 log 這個 method,就這樣一直不斷找下去。找到某個東西的__proto__是 null 為止。意思就是這邊是最上層了。

==而上面這一條透過__proto__不斷串起來的鍊,就叫做原型鍊==。透過這一條原型鍊,就可以達成類似繼承的功能,可以呼叫自己 parent 的 method。

其中因為Person其實就是個 Function 的 instance,所以Person.__proto__就是Function.prototype

底下是原型鍊的解釋 code

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}

var nick = new Person('nick', 18);

// 這個剛講過了,nick.__proto__ 會指向 Person.prototype
console.log(nick.__proto__ === Person.prototype) // true

// 那 Person.prototype.__proto__ 會指向誰呢?會指向 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(Person.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype) // true

// 那 Object.prototype.__proto__ 又會指向誰呢?會指向 null,這就是原型鍊的頂端了
console.log(Object.prototype.__proto__) // null

如果想知道一個屬性是存在 instance 身上,還是存在於它屬於的原型鍊當中,可以用hasOwnProperty這個方法:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}

var nick = new Person('nick', 18);
console.log(nick.hasOwnProperty('log')); // false
console.log(nick.__proto__.hasOwnProperty('log')); // true

new 做了什麼

有了原型鍊的概念之後,就不難理解new這個關鍵字背後會做的事情是什麼。

假設現在有一行程式碼是:var nick = new Person('nick');,那它有以下幾件事情要做:

  1. 創出一個新的 object,我們叫它 O
  2. 把 O 的 __proto__ 指向 Person 的 prototype,才能繼承原型鍊
  3. 拿 O 當作 context,呼叫 Person 這個建構函式
  4. 回傳 O
    我們可以寫一段程式碼來模擬這個情形:
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}

function newObj(Constructor, arguments) {
  var o = new Object();

  // 讓 o 繼承原型鍊
  o.__proto__ = Constructor.prototype;

  // 執行建構函式
  Constructor.apply(o, arguments);

  // 回傳建立好的物件
  return o;
}

var nick = newObj(Person, ['nick', 18]);
nick.log(); // nick, age:18

new 的教學影片範例:

function Dog(name) {
  this.name = name
}

Dog.prototype.getName = function() {
  return this.name
}

Dog.prototype.sayHello = function() {
  console.log(this.name)
}

//var d = new Dog('hi')

var b = newDog('hello')
b.sayHello()

// newDog() 要做 new 做的事
function newDog(name) {
  var obj = {} // 建立新的 obj

  Dog.call(obj, name)
  // 呼叫任意 constructor,這邊呼叫 Dog
  // 把 obj 當作 constructor 的 this 丟進去
  // 所以 Dog 內的 this 就是這邊傳入的 obj
  console.log(obj) // 此時的 obj => { name: 'hello' }

  obj.__proto__ = Dog.prototype
  // 設定要關聯的 prototype
  // 就可以使用上面的 Dog.prototype 相關的 function

  return obj
}

小補充:

function test() {
  console.log(this)
}

test.call({}) // {}
// 使用 call 呼叫時,
// test 函式內的 this 就會對應到 call() 傳入的參數

還有 instanceof, constructor 等函式,詳見參考資料
參考資料:該來理解 JavaScript 的原型鍊了

tags: Week16

#week16







Related Posts

this 是怎樣

this 是怎樣

使用 Python 進行中斷點 step by step debug 除錯

使用 Python 進行中斷點 step by step debug 除錯

Reactive Programming 簡介與教學(以 RxJS 為例)

Reactive Programming 簡介與教學(以 RxJS 為例)


Comments